#ifndef __AVR_ATmega88PA__
#define __AVR_ATmega88PA__
#endif
#ifndef F_CPU
#define F_CPU 8000000
#endif
#define bv(x) _BV(x)
#define nop() __asm__ __volatile__("nop"::)

#include <stdio.h>
#include <stdint.h>
#include <util/delay.h>
#include <avr/io.h>
#include <avr/builtins.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>

static unsigned char read_ext(unsigned char);
static unsigned char send_spi(unsigned char);
static void write_ext(unsigned char, unsigned char);

int main(void) {

    uint8_t hh;
    uint8_t mm;
    uint8_t ss;
    uint8_t in;
    DDRD = 0xff;
    DDRC = bv(PC1) | bv(PC5); //PC5:detik PC1:RCK&G (STCP&OE)
    DDRB = ~bv(PB4); //PB4 input IRQ, sisanya output

    PORTC = ~bv(PC5) & ~bv(PC1); //pullup enable semua PORTC kecuali PC1 & PC5
    PORTB = bv(PB4); //pullup untuk IRQ

    SPCR = bv(SPE) | bv(MSTR) | bv(SPR0) | bv(SPR1);
    //SPSR = bv(SPI2X);

    send_spi(0); //send serial data to input buffer
    send_spi(0);
    PORTC |= bv(PC1); //output disable + load input buffer to output stage
    nop();
    PORTC &= ~bv(PC1); //output enable
    _delay_ms(500);
    in = read_ext(0xb);
    _delay_us(100);
    if ((in & 0b11111) != 0b00011010) {
        write_ext(0xb, 0b00011010);
        _delay_us(1);
    }
    //0b00011010
    write_ext(0xa, 0b00101111);
    _delay_us(100);
    read_ext(0xc);
    _delay_us(100);

    uint8_t state = 0;
    uint8_t count_a = 0;
    uint8_t count_b = 0;

    /*
     * !IRQ ke PB4 (PCINT4), PCI0
     * SQW ke PC0 (PCINT8), PCI1
     */
    PCICR = bv(PCIE0) | bv(PCIE1);
    PCMSK1 = bv(PCINT8);
    PCMSK0 = bv(PCINT4);
    sei();
    read_ext(0xc);
    _delay_us(1);
   while (0) {
        send_spi(__builtin_avr_swap(state));
        PORTC |= bv(PC1);
        nop();
        PORTC &= ~bv(PC1);
        if (state >= 0x99) {
            state = 0;
        } else if ((state & 0xF) == 9) {
            state += 7;
        } else {
            state++;
        }
        _delay_ms(500);
    }
    while (1) {
        if (state == 0) {
            in = (PINC & (_BV(PC3) | _BV(PC4)));
            switch (in) {
                case 0b11000:
                    count_a = 0;
                    count_b = 0;
                    break;
                case 0b10000:
                    count_a++; //PC3
                    break;
                case 0b01000:
                    count_b++; //PC4
                    break;
                default:
                    count_a++;
                    count_b++;
                    break;
            }
            if (count_a == 0 && count_b > 250) {
                state = 1;
                count_b = 0;
            } else if (count_a > 250 && count_b == 0) {
                state = 2;
                count_a = 0;
            }


        } else if (state == 1) {
            /*
             * Edit Jam dan tanggal
             */
            //uint8_t temp;
            cli();
            read_ext(0xc);
            _delay_us(100);
            write_ext(0xb, 0b10011010); //matikan update SET=1
            _delay_us(100);
            write_ext(0xa, 0b01101111);
            _delay_us(100);
            hh = read_ext(0x4);
            _delay_us(100);
            mm = read_ext(0x2);
            ss = 0x00;
            uint8_t hari = read_ext(0x6);
            uint8_t DD = read_ext(0x7);
            uint8_t MM = read_ext(0x8);
            uint8_t YY = read_ext(0x9);
            while (!(PINC & bv(PC4))) {
                _delay_ms(1);
            }
            while (1) {
                /*Edit jam*/
                send_spi(0x10); //Menu 1
                send_spi(__builtin_avr_swap(hh));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (hh >= 0x23) {
                        hh = 0;
                    } else if ((hh & 0xF) == 9) {
                        hh += 7;
                    } else {
                        hh++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            while (1) {
                /*Edit menit*/
                send_spi(0x20); //Menu 2
                send_spi(__builtin_avr_swap(mm));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (mm >= 0x59) {
                        mm = 0;
                    } else if ((mm & 0xF) == 9) {
                        mm += 7;
                    } else {
                        mm++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            while (1) {
                /*Edit hari*/
                send_spi(0x30); //Menu 3
                send_spi(__builtin_avr_swap(hari));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (hari >= 0x07) {
                        hari = 1;
                    } else {
                        hari++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            while (1) {
                /*Edit tanggal*/
                send_spi(0x40); //Menu 4
                send_spi(__builtin_avr_swap(DD));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (DD >= 0x31) {
                        DD = 1;
                    } else if ((DD & 0xF) == 9) {
                        DD += 7;
                    } else {
                        DD++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            while (1) {
                /*Edit bulan*/
                send_spi(0x50); //Menu 5
                send_spi(__builtin_avr_swap(MM));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (MM >= 0x12) {
                        MM = 1;
                    } else if ((MM & 0xF) == 9) {
                        MM += 7;
                    } else {
                        MM++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            while (1) {
                /*Edit tahun*/
                send_spi(0x60); //Menu 6
                send_spi(__builtin_avr_swap(YY));
                PORTC |= bv(PC1);
                nop();
                PORTC &= ~bv(PC1);
                if (!(PINC & bv(PC3))) {
                    if (YY >= 0x50) {
                        YY = 0;
                    } else if ((YY & 0xF) == 9) {
                        YY += 7;
                    } else {
                        YY++;
                    }
                    _delay_ms(300); //guard delay sebelum eksekusi if selanjutnya
                } else if (!(PINC & bv(PC4))) {
                    while (!(PINC & bv(PC4))) {
                        _delay_us(300);
                    }
                    break;
                }

            }
            write_ext(0xa, 0b01101111); //turn off oscillator
            _delay_us(1);
            write_ext(2, mm);
            _delay_us(1);
            write_ext(4, hh);
            _delay_us(1);
            write_ext(6, hari);
            _delay_us(1);
            write_ext(7, DD);
            _delay_us(1);
            write_ext(8, MM);
            _delay_us(1);
            write_ext(9, YY);
            _delay_us(1);
            write_ext(0xa, 0b00101111); //turn on oscillator
            _delay_us(1);
            write_ext(0xb, 0b00011010); //SET=0
            _delay_us(1);
            state = 0;
            read_ext(0xc);
            _delay_us(1);
            sei();
        } else if (state == 2) {
            /*
             * Show date and month
             */
            cli();
            uint8_t DDa = read_ext(0x07);
            _delay_us(100);
            uint8_t MMa = read_ext(0x8);
            _delay_us(100);
            uint8_t YYa = read_ext(0x9);
            _delay_us(100);
            send_spi(__builtin_avr_swap(DDa));
            send_spi(__builtin_avr_swap(MMa));
            PORTC |= bv(PC5);
            PORTC |= bv(PC1);
            nop();
            PORTC &= ~bv(PC1);

            _delay_ms(3000);
            send_spi(__builtin_avr_swap(0x20));
            send_spi(__builtin_avr_swap(YYa));
            PORTC &= ~bv(PC5);
            PORTC |= bv(PC1);
            nop();
            PORTC &= ~bv(PC1);
            _delay_ms(2000);
            state = 0;
            read_ext(0xc);
            _delay_us(100);
            sei();
        }
        _delay_ms(1); // Delay 1 putaran normal
    }
    return 0;
}

static unsigned char send_spi(unsigned char x) {
    SPDR = x;
    while (!(SPSR & bv(SPIF)));
    return SPDR;
}

static unsigned char read_ext(unsigned char addr) {
    unsigned char temp;
    PORTB |= bv(PB1); //PB1 = r/!w
    nop();
    nop();
    PORTB |= bv(PB2); //PB2 = DS
    nop();
    PORTD = addr;
    PORTB |= bv(PB0); //AS address strobe
    nop();
    nop();
    nop();
    PORTB &= ~bv(PB0);
    nop();
    nop();
    PORTD = 0xff; // pullup
    DDRD = 0; //input
    _delay_us(1);
    nop();
    PORTB &= ~bv(PB2);
    nop();
    nop();
    nop();
    temp = PIND;
    nop();
    PORTB |= bv(PB2);
    DDRD = 0xff;
    nop();
    nop();
    nop();
    PORTB &= ~bv(PB1);
    nop();
    nop();
    PORTB &= ~bv(PB2);
    nop();
    nop();
    return temp;
}

static void write_ext(unsigned char addr, unsigned char value) {
    PORTB |= bv(PB1); //PB1 = r/!w
    nop();
    nop();
    PORTB |= bv(PB2); //PB2 = DS
    nop();
    DDRD = 0xff;
    _delay_us(1); //ds to as delay
    PORTD = addr;
    PORTB |= bv(PB0); //AS address strobe
    _delay_us(10);
    PORTB &= ~bv(PB0); //deassert AS, latch address
    nop();
    nop();
    PORTB &= ~bv(PB1); //assert W
    PORTD = value;
    _delay_us(10);
    PORTB |= bv(PB1);
    nop();
    nop();
    nop();
    PORTB &= ~bv(PB1);
    nop();
    nop();
    PORTB &= ~bv(PB2);
}

ISR(PCINT0_vect) {
    uint8_t hh;
    uint8_t mm;
    uint8_t irq;
    if (!(PINB & bv(PB4))) {
        irq = read_ext(0xc);
        _delay_us(10);
        // UIF (update-ended interrupt flag di bitmask 4
        if (irq & bv(4)) {
            mm = read_ext(0x2);
            _delay_us(10);
            hh = read_ext(0x4);
            send_spi(__builtin_avr_swap(hh));
            send_spi(__builtin_avr_swap(mm));
            PORTC |= bv(PC1);
            nop();
            PORTC &= ~bv(PC1);
        }
    }
}

ISR(PCINT1_vect) {
    if (PINC & bv(PC0)) {
        PORTC = PORTC ^ bv(PC5);
    }
}